﻿
// **************************************************************************
//    Class Created by Mick Doherty (Dotnetrix) June 2010
//    http://dotnetrix.co.uk/
//
//    ...for GlassUI (an AeroGlass UI Library)
//    http://glassui.codeplex.com/
// **************************************************************************

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using GlassUI.Design;

namespace GlassUI
{
    [ToolboxBitmap(typeof(System.Windows.Forms.ListView))]
    public class GlassListView : ListView
    {
        #region Constructor

        public GlassListView()
            : base()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true);
        }

        #endregion

        #region Destructor

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void Dispose(bool disposing)
        {
            if (disposing)
                if (header != null)
                    header.ReleaseHandle();
            base.Dispose(disposing);
        }

        #endregion

        #region Private Instance variables

        private bool defaultToGlass = true;

        private static Color defaultGlowColor = SystemColors.Highlight;
        private static Color ambientGlowColor = Color.Empty;
        private Color glowColor = ambientGlowColor;

        private Color groupForeColor = Color.Empty;

        private Color headerBackColor = SystemColors.Control;
        private Color headerForeColor = SystemColors.ControlText;

        private NativeHeader header;

        #endregion

        #region Events

        public event EventHandler<DrawListViewGroupEventArgs> DrawGroup;

        #endregion

        #region properties

        [Category("Behavior")]
        [DefaultValue(true)]
        [GlassUIDescription("DefaultToGlassDescription")]
        public bool DefaultToGlass
        {
            get { return defaultToGlass; }
            set { defaultToGlass = value; }
        }

        public override System.Drawing.Color BackColor
        {
            get
            {
                GlassForm form = this.FindForm() as GlassForm;
                if (form != null)
                {
                    if (DefaultToGlass && form.ControlIsOnGlass(this))
                        return Color.Transparent;
                }
                return base.BackColor;
            }
            set
            {
                base.BackColor = value;
            }
        }

        [Category("Appearance")]
        [AmbientValue(typeof(Color), "Empty")]
        [GlassUIDescription("GlowColorDescription")]
        public Color GlowColor
        {
            get
            {
                if (this.glowColor == Color.Empty && this.Parent != null)
                {
                    Color color = ReflectedGlowColor(this.Parent);
                    if (!color.IsEmpty)
                        return color;
                    return GlassListView.defaultGlowColor;
                }
                return this.glowColor;
            }
            set { glowColor = value; }
        }

        public void ResetGlowColor()
        {
            this.GlowColor = GlassListView.ambientGlowColor;
        }

        private bool ShouldSerializeGlowColor()
        {
            return (this.glowColor != GlassListView.ambientGlowColor);
        }

        [AmbientValue(typeof(Color), "Empty")]
        [Category("Appearance")]
        [GlassUIDescription("ListViewGroupForeColorDescription")]
        public Color GroupForeColor
        {
            get 
            {
                if (groupForeColor.IsEmpty)
                {
                    if (Application.RenderWithVisualStyles)
                    {
                        VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.ExplorerBar.NormalGroupHead.Normal);
                        return renderer.GetColor(ColorProperty.TextColor);
                    }
                    return this.ForeColor; 
                }
                return groupForeColor;
            }
            set
            {
                if (groupForeColor != value)
                {
                    groupForeColor = value;
                    Invalidate();
                }
            }
        }

        private bool ShouldSerializeGroupForeColor()
        {
            return !groupForeColor.IsEmpty;
        }
        private void ResetGroupForeColor()
        {
            GroupForeColor = Color.Empty;
        }

        [Category("Appearance")]
        [DefaultValue(typeof(Color),"Control")]
        [GlassUIDescription("ListViewHeaderBackColorDescription")]
        public Color HeaderBackColor
        {
            get
            {
                GlassForm form = this.FindForm() as GlassForm;
                if (form != null)
                {
                    if (DefaultToGlass && form.ControlIsOnGlass(this))
                        return Color.Transparent;
                }
                return headerBackColor;
            }
            set
            {
                if (headerBackColor != value)
                {
                    headerBackColor = value;
                    if (header != null)
                        header.Invalidate();
                }
            }
        }

        [Category("Appearance")]
        [DefaultValue(typeof(Color), "ControlText")]
        [GlassUIDescription("ListViewHeaderForeColorDescription")]
        public Color HeaderForeColor
        {
            get
            {
                return headerForeColor;
            }
            set
            {
                if (headerForeColor != value)
                {
                    headerForeColor = value;
                    if (header != null)
                        header.Invalidate();
                }
            }
        }

        #endregion

        #region Overridden/Virtual Methods

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            foreach (ListViewItem item in this.Items)
            {
                ListViewItemStates state = ListViewItemStates.Default;

                if (item.Checked)
                    state |= ListViewItemStates.Checked;

                if (item.Focused)
                    state |= ListViewItemStates.Focused;

                if (HotItemInternal() == item.Index)
                    state |= ListViewItemStates.Hot;

                if (item.Selected)
                    state |= ListViewItemStates.Selected;

                Rectangle rc = this.FullRowSelect ? item.Bounds : item.GetBounds(ItemBoundsPortion.ItemOnly);

                DrawListViewItemEventArgs ev = new DrawListViewItemEventArgs(e.Graphics, item, rc, item.Index, state);
                OnDrawItem(ev);

                if (this.View == View.Details)
                {
                    foreach (ListViewItem.ListViewSubItem subItem in item.SubItems)
                    {
                        int columnIndex = item.SubItems.IndexOf(subItem);
                        ColumnHeader column = Columns.Count > columnIndex ?this.Columns[item.SubItems.IndexOf(subItem)] : null;
                        DrawListViewSubItemEventArgs evs = new DrawListViewSubItemEventArgs(e.Graphics, subItem.Bounds, item, subItem, item.Index, item.SubItems.IndexOf(subItem), column, state);
                        OnDrawSubItem(evs);
                    }
                }
            }


            if (this.ShowGroups && this.Groups.Count > 0 && this.View != View.List)
            {
                int groupCount = this.GroupCountInternal();

                for (int id = 0; id < groupCount; id++)
                {
                    ListViewGroupItem group = this.GroupInfoInternal(id);
                    Rectangle rc = GroupBoundsInternal(group.GroupId);

                    if (group.GroupId == this.HotGroupInternal())
                        group.State |= ListViewItemStates.Hot;

                    if (this.OwnerDraw)
                    {
                        DrawListViewGroupEventArgs ev = new DrawListViewGroupEventArgs(e.Graphics, group.HeaderText, this.Font, rc, group.State);
                        OnDrawGroup(ev);
                    }
                    else
                    {
                        DrawGroupItem(e.Graphics, group.HeaderText, rc, group.HeaderTextAlign, group.State);
                    }
                }
            }

        }

        protected override void OnDrawItem(DrawListViewItemEventArgs e)
        {
            if (this.OwnerDraw)
                base.OnDrawItem(e);
            else
            {
                Font itemFont = e.Item.Font;
                Font subItemFont = itemFont;

                bool hot = (e.State & ListViewItemStates.Hot) == ListViewItemStates.Hot;
                if (hot && this.HotTracking)
                    itemFont = new Font(e.Item.Font, FontStyle.Underline);

                bool selected = (e.State & ListViewItemStates.Selected) == ListViewItemStates.Selected;
                e.Graphics.SmoothingMode = SmoothingMode.HighQuality;

                Rectangle imageBounds = e.Item.GetBounds(ItemBoundsPortion.Icon);
                if (e.Item.ImageList != null)
                {
                    if (this.View == View.LargeIcon)
                    {
                        imageBounds.Size = e.Item.ImageList.ImageSize;
                        imageBounds.Offset(SystemInformation.SmallIconSize.Width + 3, 0);
                        if (this.CheckBoxes)
                            imageBounds.Offset(SystemInformation.MenuCheckSize.Width + 6, 0);
                    }
                }

                Rectangle textBounds = e.Item.GetBounds(ItemBoundsPortion.Label);
                textBounds.Inflate(2, 0);
                Rectangle subTextBounds = Rectangle.Empty;

                TextFormatFlags format = TextFormatFlags.Left | TextFormatFlags.NoClipping | TextFormatFlags.TextBoxControl;

                if (LabelWrap && (this.View == View.LargeIcon || this.View == View.SmallIcon))
                    format |= TextFormatFlags.WordBreak;
                else
                    format |= TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter;


                Size textSize = GlassUtilities.MeasureThemedText(e.Graphics, e.Item.Text, itemFont, textBounds.Size, format, 4);
                ListViewItem.ListViewSubItem subItem = null;

                if (this.View == View.Tile && e.Item.SubItems.Count > 1)
                {
                    subItem = e.Item.SubItems[1];
                    subItemFont = new Font(e.Item.Font, itemFont.Style);
                    Size subTextSize = GlassUtilities.MeasureThemedText(e.Graphics, subItem.Text, subItemFont, textBounds.Size, format, 4);
                    subTextBounds = new Rectangle(textBounds.Left, textSize.Height, Math.Max(textSize.Width, subTextSize.Width), subTextSize.Height);
                }

                textBounds.Width = Math.Max(textSize.Width, subTextBounds.Width);

                if (selected | e.Item.Focused)
                {
                    Rectangle itemBounds = textBounds;
                    if (FullRowSelect && this.View != View.LargeIcon)
                    {
                        itemBounds = e.Item.GetBounds(ItemBoundsPortion.ItemOnly);
                        itemBounds.Inflate(2, 2);
                        itemBounds.X += imageBounds.Width;
                        itemBounds.Width -= imageBounds.Width;
                    }
                    if (selected)
                    {
                        using (GraphicsPath path = GlassUtilities.RoundedRectangle(itemBounds, 4))
                        {
                            using (Brush brush = new SolidBrush(Color.FromArgb(192, this.GlowColor)))
                                e.Graphics.FillPath(brush, path);
                        }
                    }
                    if (Focused)
                    {
                        itemBounds.Inflate(-2, -2);
                        ControlPaint.DrawFocusRectangle(e.Graphics, itemBounds);
                    }
                }

                Image image = null;
                if (e.Item.ImageList != null)
                {
                    if (e.Item.ImageIndex != -1)
                        image = e.Item.ImageList.Images[e.Item.ImageIndex];
                    else if (e.Item.ImageKey.Length > 0)
                        image = e.Item.ImageList.Images[e.Item.ImageKey];
                    else
                    {
                        Size sz = e.Item.ImageList.ImageSize;
                        image = new Bitmap(sz.Width, sz.Height, PixelFormat.Format32bppPArgb);
                    }
                }

                if (this.CheckBoxes)
                {
                    Image stateImage;
                    if (this.StateImageList != null)
                    {
                        int id = e.Item.StateImageIndex == -1 ? 0 : e.Item.StateImageIndex;
                        stateImage = new Bitmap(this.StateImageList.Images[id]);
                    }
                    else
                    {
                        Size sz = SystemInformation.MenuCheckSize;
                        stateImage = new Bitmap(sz.Width, sz.Height, PixelFormat.Format32bppPArgb);
                        using (Graphics g = Graphics.FromImage(stateImage))
                            CheckBoxRenderer.DrawCheckBox(g, Point.Empty, e.Item.Checked ? CheckBoxState.CheckedNormal : CheckBoxState.UncheckedNormal);
                    }
                    Rectangle checkBounds = imageBounds;
                    checkBounds.X -= stateImage.Width;
                    checkBounds.Y += (imageBounds.Height - stateImage.Height) / 2;
                    e.Graphics.DrawImageUnscaled(stateImage, checkBounds);
                }

                if (image != null)
                    e.Graphics.DrawImageUnscaled(image, imageBounds);
                GlassUtilities.DrawThemedText(e.Graphics, e.Item.Text, itemFont, textBounds, this.headerForeColor, format, 4);

                if (subItem != null && StringWidthInternal(e.Item.Text) <= textBounds.Width)
                {
                    Size firstLineSize = GlassUtilities.MeasureThemedText(e.Graphics, e.Item.Text, e.Item.Font, textBounds.Size, format, 4);
                    textBounds.Y += firstLineSize.Height;
                    textBounds.Height -= firstLineSize.Height;
                    GlassUtilities.DrawThemedText(e.Graphics, subItem.Text, subItemFont, textBounds, subItem.ForeColor, format, 4);
                }
            }
        }

        protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
        {
            if (this.OwnerDraw)
                base.OnDrawSubItem(e);
            else
            {
                if (e.Item.SubItems.IndexOf(e.SubItem)!= 0)
                {
                    Font itemFont = e.Item.Font;

                    bool hot = (e.ItemState & ListViewItemStates.Hot) == ListViewItemStates.Hot;

                    if (hot && this.HotTracking)
                        itemFont = new Font(e.Item.Font, FontStyle.Underline);

                    TextFormatFlags format = TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine | TextFormatFlags.TextBoxControl | TextFormatFlags.NoClipping;
                    GlassUtilities.DrawThemedText(e.Graphics, e.SubItem.Text, itemFont, e.SubItem.Bounds, e.SubItem.ForeColor, format, 8);
                }
            }
        }

        protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
        {
            if (this.OwnerDraw)
                base.OnDrawColumnHeader(e);
            else
            {
                Image image = null;
                if (e.Header.ImageList != null)
                {
                    if (e.Header.ImageIndex!=-1)
                        image = e.Header.ImageList.Images[e.Header.ImageIndex];
                    if (e.Header.ImageKey.Length > 0)
                        image = e.Header.ImageList.Images[e.Header.ImageKey];
                }
                DrawHeaderItem(e.Graphics, e.Header.Text, image, e.Bounds, e.Header.TextAlign, e.State);
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        [SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void WndProc(ref Message m)
        {

            if (m.Msg == (int)NativeMethods.USER32.WindowMessage.WM_PARENTNOTIFY)
            {
                if ((ushort)(m.WParam.ToInt32() & 0xFFFF) == (int)NativeMethods.USER32.WindowMessage.WM_CREATE)
                {
                    System.Text.StringBuilder windowName = new System.Text.StringBuilder(16);
                    int result = NativeMethods.USER32.RealGetWindowClass(m.LParam, windowName, 16);
                    if (result == 0)
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    if (windowName.ToString() == "SysHeader32")
                    {
                        if (header != null)
                            header.ReleaseHandle();
                        header = new NativeHeader(this);
                        header.AssignHandle(m.LParam);
                    }
                }
            }

            base.WndProc(ref m);

            switch (m.Msg)
            {
                case (int)NativeMethods.USER32.WindowMessage.WM_DRAWITEM:
                    NativeMethods.COMCTL32.DRAWITEMSTRUCT dis = (NativeMethods.COMCTL32.DRAWITEMSTRUCT)m.GetLParam(typeof(NativeMethods.COMCTL32.DRAWITEMSTRUCT));
                    if (dis.CtlType == NativeMethods.COMCTL32.ODT_HEADER)
                    {
                        ListViewItemStates state = (ListViewItemStates)dis.itemState;
                        if (state == 0)
                            state = ListViewItemStates.Default;
                        if (dis.itemID == header.GetHotItem() && state != ListViewItemStates.Selected)
                            state = ListViewItemStates.Hot;
                        if (dis.hDC != IntPtr.Zero)
                        {
                            using (Graphics g = Graphics.FromHdc(dis.hDC))
                            {
                                DrawListViewColumnHeaderEventArgs ev = new DrawListViewColumnHeaderEventArgs(
                                    g, dis.rcItem.ToRectangle(), dis.itemID, this.Columns[dis.itemID], state, this.HeaderForeColor, this.HeaderBackColor, this.Font);
                                this.OnDrawColumnHeader(ev);
                            }
                        }
                        m.Result = (IntPtr)1;
                    }
                    break;
                //default:
                //    if (Enum.IsDefined(typeof(NativeMethods.COMCTL32.ListViewMessage), m.Msg))
                //        Console.WriteLine(((NativeMethods.COMCTL32.ListViewMessage)m.Msg).ToString());
                //    break;
            }

        }

        protected virtual void OnDrawGroup(DrawListViewGroupEventArgs e)
        {
            if (this.DrawGroup != null)
                this.DrawGroup(this, e);
        }

        #endregion

        #region Private/Internal methods

        private void DrawGroupItem(Graphics graphics, string text, Rectangle bounds, HorizontalAlignment textAlignment, ListViewItemStates state)
        {
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                if ((state & ListViewItemStates.Hot) == ListViewItemStates.Hot)
                    DrawItemBackground(graphics, bounds, ListViewItemStates.Default);

            bounds.Inflate(-10, 0);
            TextFormatFlags format = TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis;

            if (textAlignment == HorizontalAlignment.Right)
                format |= TextFormatFlags.Right;

            if (textAlignment == HorizontalAlignment.Center)
                format |= TextFormatFlags.HorizontalCenter;

            GlassUtilities.DrawThemedText(graphics, text, this.Font, bounds, this.GroupForeColor, format, 8);
            Size sz = GlassUtilities.MeasureThemedText(graphics, text, this.Font, bounds.Size, format, 8);

            int vCenter = bounds.Top + (bounds.Height / 2);
            
            Rectangle clipRect = new Rectangle(bounds.Location, new Size(sz.Width,bounds.Height));

            switch (textAlignment)
            {
                case HorizontalAlignment.Center:
                    clipRect.Offset((bounds.Width/2) - (clipRect.Width/2), 0);
                    break;
                case HorizontalAlignment.Right:
                    clipRect.Offset(bounds.Width - clipRect.Width, 0);
                    break;
                default:
                    break;
            }

            graphics.SetClip(clipRect, System.Drawing.Drawing2D.CombineMode.Exclude);

            using (Pen pen = new Pen(Color.FromArgb(196, this.GroupForeColor)))
            {
                graphics.DrawLine(pen, new Point(bounds.Left, vCenter), new Point(bounds.Right-1, vCenter));
                pen.Color = Color.FromArgb(196, Color.White);
                vCenter++;
                graphics.DrawLine(pen, new Point(bounds.Left, vCenter), new Point(bounds.Right-1, vCenter));
            }

            graphics.ResetClip();
            clipRect.Inflate(-4, -4);
            if ((state & ListViewItemStates.Focused) == ListViewItemStates.Focused)
                ControlPaint.DrawFocusRectangle(graphics, clipRect);

        }

        private void DrawHeaderItem(Graphics graphics, string text, Image image, Rectangle bounds, HorizontalAlignment textAlignment, ListViewItemStates state)
        {
            if (this.HeaderBackColor.A < 255)
            {
                graphics.SetClip(bounds);
                DrawParentBackground(graphics);
                graphics.ResetClip();
            }

            using (Brush brush = new SolidBrush(this.HeaderBackColor))
                graphics.FillRectangle(brush, bounds);

            DrawItemBackground(graphics, bounds, state);

            bounds.Inflate(-4, -2);

            System.Drawing.ContentAlignment align = System.Drawing.ContentAlignment.MiddleLeft;
            if (textAlignment == HorizontalAlignment.Center)
                align = System.Drawing.ContentAlignment.MiddleCenter;
            if (textAlignment == HorizontalAlignment.Right)
                align = System.Drawing.ContentAlignment.MiddleRight;

            GlassUtilities.RenderStandardContent(graphics, bounds, text, Font, image, align, align, TextImageRelation.ImageBeforeText, this.HeaderForeColor, false, 4, true, TextFormatFlags.EndEllipsis | TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter | TextFormatFlags.TextBoxControl);
             
        }

        private void DrawItemBackground(Graphics graphics, Rectangle bounds, ListViewItemStates state)
        {
            if (bounds.Width < 5 || bounds.Height < 5)
                return;

            // Set up smooth drawing
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;

            GlassForm form = this.FindForm() as GlassForm;

            bool glassify = form == null ? false : form.ControlIsOnGlass(this.Parent);

            Color topColor = state == ListViewItemStates.Selected ? this.GlowColor : Color.FromArgb(128, Color.White);
            Color bottomColor = Color.FromArgb(128, Color.Black);

            Rectangle rc = bounds;
            rc.Width--;
            rc.Height--;

            GraphicsPath path = GlassUtilities.RoundedRectangle(rc, 4);

            if (!glassify)
            {
                using (Brush brush = new SolidBrush(this.HeaderBackColor))
                    graphics.FillPath(brush, path);
            }

            // Fill with the Gradient
            using (LinearGradientBrush brush = new LinearGradientBrush(rc, topColor, bottomColor, LinearGradientMode.Vertical))
            {
                brush.WrapMode = WrapMode.TileFlipX;
                graphics.FillPath(brush, path);
            }

            // Don't blend glow
            graphics.CompositingMode = CompositingMode.SourceOver;

            // Draw a Glow
            if ((state & ListViewItemStates.Hot) == ListViewItemStates.Hot && !this.DesignMode)
            {
                Rectangle hdrBounds = rc;
                hdrBounds.Offset(0, bounds.Height / 2);

                using (GraphicsPath glowPath = new GraphicsPath())
                {
                    glowPath.AddEllipse(hdrBounds);

                    using (PathGradientBrush brush = new PathGradientBrush(glowPath))
                    {
                        brush.CenterColor = this.GlowColor;
                        brush.SurroundColors = new Color[] { Color.Transparent };
                        Region clipRgn = graphics.Clip;
                        graphics.SetClip(path, CombineMode.Replace);
                        graphics.FillPath(brush, glowPath);
                        graphics.SetClip(clipRgn, CombineMode.Replace);
                    }
                }
            }

            Color glass = this.HeaderBackColor;

            if (GlassUtilities.GlassEnabled)
                glass = GlassUtilities.SystemGlassColor;

            // Draw the border
            using (Pen pen = new Pen(Color.FromArgb(Math.Max(192, (int)glass.A), glass)))
            {
                graphics.DrawPath(pen, path);
                rc.Inflate(-1, -1);
                path.Reset();
                path.AddPath(GlassUtilities.RoundedRectangle(rc, 3), false);
                pen.Color = Color.FromArgb(192, Color.White);
                graphics.DrawPath(pen, path);
            }

            path.Dispose();
        }

        internal void DrawParentBackground(Graphics graphics)
        {
            System.Drawing.Drawing2D.GraphicsContainer g = graphics.BeginContainer();
            Rectangle translateRect = this.Bounds;
            graphics.TranslateTransform(-this.Left, -this.Top);
            PaintEventArgs pe = new PaintEventArgs(graphics, translateRect);
            this.InvokePaintBackground(this.Parent, pe);
            this.InvokePaint(this.Parent, pe);
            graphics.ResetTransform();
            graphics.EndContainer(g);
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        private Rectangle GroupBoundsInternal(int index)
        {
            NativeMethods.USER32.RECT rc = new NativeMethods.USER32.RECT();
            rc.top = (int)NativeMethods.COMCTL32.LVGETGROUPRECT.LVGGR_HEADER;
            IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(rc));
            Marshal.StructureToPtr(rc, lParam, true);
            NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.ListViewMessage.LVM_GETGROUPRECT, (IntPtr)index, lParam);
            rc = ((NativeMethods.USER32.RECT)Marshal.PtrToStructure(lParam, typeof(NativeMethods.USER32.RECT)));
            Marshal.FreeCoTaskMem(lParam);
            return rc.ToRectangle();
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        private ListViewGroupItem GroupInfoInternal(int index)
        {
            ListViewGroupItem item = new ListViewGroupItem();
            NativeMethods.COMCTL32.LVGROUP group = new NativeMethods.COMCTL32.LVGROUP();
            group.cchHeader = 256;
            group.pszHeader = Marshal.AllocHGlobal(256);
            group.cchFooter = 256;
            group.pszFooter = Marshal.AllocHGlobal(256);
            group.cbSize = Marshal.SizeOf(group);
            group.mask = NativeMethods.COMCTL32.LVGROUPMASK.LVGF_HEADER |
                NativeMethods.COMCTL32.LVGROUPMASK.LVGF_STATE |
                NativeMethods.COMCTL32.LVGROUPMASK.LVGF_ALIGN |
                NativeMethods.COMCTL32.LVGROUPMASK.LVGF_GROUPID;
            group.stateMask = NativeMethods.COMCTL32.LVGROUPSTATE.LVGS_SELECTED |
                NativeMethods.COMCTL32.LVGROUPSTATE.LVGS_NORMAL |
                NativeMethods.COMCTL32.LVGROUPSTATE.LVGS_FOCUSED;

            IntPtr lParam = Marshal.AllocHGlobal(group.cbSize);
            Marshal.StructureToPtr(group, lParam, true);

            NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.ListViewMessage.LVM_GETGROUPINFOBYINDEX, (IntPtr)index, lParam);

            group = ((NativeMethods.COMCTL32.LVGROUP)Marshal.PtrToStructure(lParam, typeof(NativeMethods.COMCTL32.LVGROUP)));

            item.GroupId = group.iGroupId;
            item.HeaderText = Marshal.PtrToStringAuto(group.pszHeader, group.cchHeader);
            item.FooterText = Marshal.PtrToStringAnsi(group.pszFooter, group.cchFooter);

            item.HeaderText = item.HeaderText.Substring(0, item.HeaderText.IndexOf("\0", StringComparison.Ordinal));
            item.FooterText = item.FooterText.Substring(0, item.FooterText.IndexOf("\0", StringComparison.Ordinal));

            item.HeaderTextAlign = group.uAlign == NativeMethods.COMCTL32.LVGROUPALIGN.LVGA_HEADER_CENTER ? HorizontalAlignment.Center :
                group.uAlign == NativeMethods.COMCTL32.LVGROUPALIGN.LVGA_HEADER_RIGHT ? HorizontalAlignment.Right : HorizontalAlignment.Left;

            if ((group.state & NativeMethods.COMCTL32.LVGROUPSTATE.LVGS_FOCUSED) == NativeMethods.COMCTL32.LVGROUPSTATE.LVGS_FOCUSED)
                item.State = ListViewItemStates.Focused;
            else if ((group.state & NativeMethods.COMCTL32.LVGROUPSTATE.LVGS_SELECTED) == NativeMethods.COMCTL32.LVGROUPSTATE.LVGS_SELECTED)
                item.State = ListViewItemStates.Selected;
            else
                item.State = ListViewItemStates.Default;

            Marshal.FreeHGlobal(lParam);
            group.Dispose();

            return item;
        }

        private int GroupCountInternal()
        {
            return NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.ListViewMessage.LVM_GETGROUPCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
        }

        private int HotItemInternal()
        {
            return NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.ListViewMessage.LVM_GETHOTITEM, IntPtr.Zero, IntPtr.Zero).ToInt32();
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        private int HotGroupInternal()
        {
            NativeMethods.COMCTL32.LVHITTESTINFO hitTest = new NativeMethods.COMCTL32.LVHITTESTINFO();
            hitTest.pt = this.PointToClient(Cursor.Position);
            IntPtr lParam = Marshal.AllocHGlobal(Marshal.SizeOf(hitTest));
            Marshal.StructureToPtr(hitTest, lParam, true);
            int result = NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.ListViewMessage.LVM_HITTEST, (IntPtr)(-1), lParam).ToInt32();
            hitTest = (NativeMethods.COMCTL32.LVHITTESTINFO)Marshal.PtrToStructure(lParam, typeof(NativeMethods.COMCTL32.LVHITTESTINFO));
            Marshal.FreeHGlobal(lParam);
            if (hitTest.flags == NativeMethods.COMCTL32.ListViewHitTestResult.LVHT_EX_GROUP_HEADER)
                return result;
            return -1;
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        private int StringWidthInternal(string text)
        {
            IntPtr lParam = Marshal.StringToHGlobalUni(text);
            int result = NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.ListViewMessage.LVM_GETSTRINGWIDTHW, IntPtr.Zero, lParam).ToInt32();
            Marshal.FreeHGlobal(lParam);
            return result;
        }

        internal static Color ReflectedGlowColor(Control parent)
        {
            PropertyInfo info = parent.GetType().GetProperty("GlowColor");
            if (info != null)
                return (Color)info.GetValue(parent, null);
            GlassForm form = parent.FindForm() as GlassForm;
            if (form != null)
                return form.GlowColor;
            return Color.Empty;
        }

        #endregion

    }

    #region Custom EventArgs

    public class DrawListViewGroupEventArgs : EventArgs
    {
        private Graphics graphics;
        private string headerText;
        private Font font;
        private Rectangle bounds;
        private ListViewItemStates state;

        public Graphics Graphics
        {
            get { return graphics; }
        }

        public string HeaderText
        {
            get { return headerText; }
        }

        public Font Font
        {
            get { return font; }
        }

        public Rectangle Bounds
        {
            get { return bounds; }
        }

        public ListViewItemStates State
        {
            get { return state; }
        }

        public DrawListViewGroupEventArgs(Graphics graphics, string text, Font font, Rectangle bounds, ListViewItemStates state)
            : base()
        {
            this.graphics = graphics;
            this.headerText = text;
            this.font = font;
            this.bounds = bounds;
            this.state = state;
        }
    }

    #endregion

    #region Nativeheader Class

    internal class NativeHeader : NativeWindow
    {
        public NativeHeader(GlassListView owner)
            : base()
        {
            this.Parent = owner;
        }

        private GlassListView Parent;

        private int hotItemID = -1;

        protected override void WndProc(ref Message m)
        {
            if (!PreWndProc(ref m))
                m.Msg = (int)NativeMethods.USER32.WindowMessage.WM_NULL;
            base.WndProc(ref m);
            PostWndProc(ref m);
        }

        private static bool PreWndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case (int)NativeMethods.COMCTL32.HeaderControlMessage.HDM_INSERTITEMA:
                case (int)NativeMethods.COMCTL32.HeaderControlMessage.HDM_INSERTITEMW:
                case (int)NativeMethods.COMCTL32.HeaderControlMessage.HDM_SETITEMA:
                case (int)NativeMethods.COMCTL32.HeaderControlMessage.HDM_SETITEMW:
                    NativeMethods.COMCTL32.HDITEM item = (NativeMethods.COMCTL32.HDITEM)m.GetLParam(typeof(NativeMethods.COMCTL32.HDITEM));
                    item.mask |= NativeMethods.COMCTL32.HDI_FORMAT;
                    item.fmt |= NativeMethods.COMCTL32.HDF_OWNERDRAW;
                    Marshal.StructureToPtr(item, m.LParam, true);
                    break;

                case (int)NativeMethods.USER32.WindowMessage.WM_ERASEBKGND:
                    m.Result = (IntPtr)1;
                    break;

            }
            return true;
        }

        private void PostWndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case (int)NativeMethods.USER32.WindowMessage.WM_PAINT:
                    int width = 0, height = 0;
                    for (int id = 0; id < this.Parent.Columns.Count; id++)
                    {
                        Rectangle rc = GetItemBounds(id);
                        width = Math.Max(width, rc.Right);
                        height = Math.Max(height, rc.Bottom);
                    }
                    Rectangle clipRectangle = new Rectangle(0, 0, width, height);
                    
                    using (Graphics g = Graphics.FromHwnd(this.Handle))
                    {
                        g.SetClip(clipRectangle, CombineMode.Exclude);
                        g.Clear(Parent.HeaderBackColor);
                    }
                    
                    break;

                case (int)NativeMethods.USER32.WindowMessage.WM_MOUSELEAVE:
                    OnMouseLeave();
                    break;

                case (int)NativeMethods.USER32.WindowMessage.WM_MOUSEMOVE:
                    OnMouseMove();
                    break;

                default:
                    //if (Enum.IsDefined(typeof(NativeMethods.USER32.WindowMessage), m.Msg))
                    //    Console.WriteLine(Enum.GetName(typeof(NativeMethods.USER32.WindowMessage), m.Msg));
                    //else if (Enum.IsDefined(typeof(NativeMethods.COMCTL32.HeaderControlMessage), m.Msg))
                    //    Console.WriteLine(Enum.GetName(typeof(NativeMethods.COMCTL32.HeaderControlMessage), m.Msg));
                    //else
                    //    Console.WriteLine(m.ToString());
                    break;
            }
        }

        private void OnMouseMove()
        {
            int hotIndex = this.GetHotItem();

            if (hotIndex == hotItemID)
                return;

            if (hotItemID != -1 && hotItemID != hotIndex)
                DrawItem(hotItemID, ListViewItemStates.Default);

            if (hotIndex != -1 && hotItemID != hotIndex)
                DrawItem(hotIndex, ListViewItemStates.Hot);

            hotItemID = hotIndex;
        }

        private void OnMouseLeave()
        {
            if (hotItemID != -1)
                DrawItem(hotItemID, ListViewItemStates.Default);
            hotItemID = -1;
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        private void DrawItem(int itemId, ListViewItemStates state)
        {
            using (Graphics g = Graphics.FromHwnd(this.Handle))
            {
                IntPtr hdc = g.GetHdc();
                NativeMethods.COMCTL32.DRAWITEMSTRUCT dis = new NativeMethods.COMCTL32.DRAWITEMSTRUCT();
                dis.CtlType = NativeMethods.COMCTL32.ODT_HEADER;
                dis.itemAction = NativeMethods.COMCTL32.ODA_DRAWENTIRE;
                dis.itemID = itemId;
                dis.itemState = state;
                dis.hDC = hdc;
                IntPtr lParam = Marshal.AllocHGlobal(Marshal.SizeOf(dis.rcItem));
                Marshal.StructureToPtr(dis.rcItem, lParam, true);
                NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.HeaderControlMessage.HDM_GETITEMRECT, (IntPtr)itemId, lParam);
                dis.rcItem = (NativeMethods.USER32.RECT)Marshal.PtrToStructure(lParam, typeof(NativeMethods.USER32.RECT));
                Marshal.FreeHGlobal(lParam);
                lParam = Marshal.AllocHGlobal(Marshal.SizeOf(dis));
                Marshal.StructureToPtr(dis, lParam, true);
                NativeMethods.USER32.SendMessage(this.Parent.Handle, NativeMethods.USER32.WindowMessage.WM_DRAWITEM, (IntPtr)itemId, lParam);
                dis = (NativeMethods.COMCTL32.DRAWITEMSTRUCT)Marshal.PtrToStructure(lParam, typeof(NativeMethods.COMCTL32.DRAWITEMSTRUCT));
                Marshal.FreeHGlobal(lParam);
                g.ReleaseHdc(hdc);
                NativeMethods.GDI32.DeleteDC(hdc);
            }
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        private Rectangle GetItemBounds(int itemId)
        {
            NativeMethods.USER32.RECT rc = new NativeMethods.USER32.RECT();
            IntPtr lParam = Marshal.AllocHGlobal(Marshal.SizeOf(rc));
            Marshal.StructureToPtr(rc, lParam, true);
            NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.HeaderControlMessage.HDM_GETITEMRECT, (IntPtr)itemId, lParam);
            rc = (NativeMethods.USER32.RECT)Marshal.PtrToStructure(lParam, typeof(NativeMethods.USER32.RECT));
            Marshal.FreeHGlobal(lParam);
            return rc.ToRectangle();
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        private int GetItemCount()
        {
            return NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.HeaderControlMessage.HDM_GETITEMCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        internal int GetHotItem()
        {
            NativeMethods.COMCTL32.HDHITTESTINFO hitTest = new NativeMethods.COMCTL32.HDHITTESTINFO();
            hitTest.pt = this.PointToClient(Cursor.Position);
            IntPtr lParam = Marshal.AllocHGlobal(Marshal.SizeOf(hitTest));
            Marshal.StructureToPtr(hitTest, lParam, true);
            NativeMethods.USER32.SendMessage(this.Handle, NativeMethods.COMCTL32.HeaderControlMessage.HDM_HITTEST, (IntPtr)(-1), lParam).ToInt32();
            hitTest = (NativeMethods.COMCTL32.HDHITTESTINFO)Marshal.PtrToStructure(lParam, typeof(NativeMethods.COMCTL32.HDHITTESTINFO));
            Marshal.FreeHGlobal(lParam);
            switch (hitTest.flags)
            {
                case NativeMethods.COMCTL32.HeaderHitTestResult.HHT_ONHEADER:
                    return hitTest.iItem;
                default:
                    return -1;
            }
        }

        internal void Invalidate()
        {
            for (int id = 0; id < this.GetItemCount(); id++)
                DrawItem(id, ListViewItemStates.Default);
        }

        internal Point PointToClient(Point pt)
        {
            int result = NativeMethods.USER32.MapWindowPoints(IntPtr.Zero, this.Handle, ref pt, 1);
            if (result == 0)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            return pt;
        }

    }

    #endregion

}
